﻿/*
This file is part of nDoctor Light

NDoctor is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

NDoctor is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with NDoctor.  If not, see <http://www.gnu.org/licenses/>.
*/
namespace Probel.Ldoc.ViewModels.Pages
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Linq;

    using Probel.Ldoc.Services.Dto;
    using Probel.Ldoc.Services.Helpers;
    using Probel.Ldoc.Services.Services;
    using Probel.Ldoc.ViewModels.Controls;
    using Probel.Ldoc.ViewModels.Dto;
    using Probel.Ldoc.ViewModels.Helpers;
    using Probel.Ldoc.ViewModels.Properties;
    using Probel.Ldoc.ViewModels.ToolBoxes;
    using Probel.Mvvm.DataBinding;
    using Probel.Mvvm.Gui;

    public class PatientEditorViewModel : BaseViewModel
    {
        #region Fields

        private readonly ICriterionFactory CriterionFactory;
        private readonly IPatientService Service;

        /// <summary>
        /// This is a temporary variable used to delay loading of the patient into the view.
        /// </summary>
        private readonly PatientCardDto ___patient;

        private bool isInitialised = false;
        private InsuranceCardDto selectedInsurance;
        private PracticeCardDto selectedPractice;
        private ProfessionCardDto selectedProfession;
        private ReputationCardDto selectedReputation;
        private UiMessageDto uiMessage = UiMessageDto.Info(Messages.Msg_Ready);

        #endregion Fields

        #region Constructors

        public PatientEditorViewModel(IErrorService errorService, ICriterionFactory criterionFactory, IPatientService service, PatientCardDto patient, bool isAsync = true)
            : base(errorService, isAsync)
        {
            this.CriterionFactory = criterionFactory;
            this.Service = service;
            this.___patient = patient;

            this.Professions = new ObservableCollection<ProfessionCardDto>();
            this.Insurances = new ObservableCollection<InsuranceCardDto>();
            this.Practices = new ObservableCollection<PracticeCardDto>();
            this.Reputations = new ObservableCollection<ReputationCardDto>();
            this.SearchTags = new ObservableCollection<TagViewModel>();
            this.AllSearchTags = new ObservableCollection<TagDto>();
            this.Doctors = new ObservableCollection<EditableDoctorCardViewModel>();

            this.CurrentPatient = new ExpandedPatientDto();
        }

        #endregion Constructors

        #region Properties

        public ObservableCollection<TagDto> AllSearchTags
        {
            get;
            private set;
        }

        public DateTime Birthdate
        {
            get { return this.CurrentPatient.Birthdate; }
            set
            {
                this.CurrentPatient.Birthdate = value;
                this.OnPropertyChanged(() => Birthdate);
            }
        }

        public string BoxNumber
        {
            get { return this.CurrentPatient.Address.BoxNumber; }
            set
            {
                this.CurrentPatient.Address.BoxNumber = value;
                this.OnPropertyChanged(() => BoxNumber);
            }
        }

        public string City
        {
            get { return this.CurrentPatient.Address.City; }
            set
            {
                this.CurrentPatient.Address.City = value;
                this.OnPropertyChanged(() => City);
            }
        }

        public string CurrentPatientName
        {
            get
            {
                return (this.___patient != null)
                    ? string.Format("{0} {1}", this.___patient.FirstName, this.___patient.LastName)
                    : "N.A.";
            }
        }

        public ObservableCollection<EditableDoctorCardViewModel> Doctors
        {
            get;
            private set;
        }

        public decimal Fee
        {
            get { return this.CurrentPatient.Fee; }
            set
            {
                this.CurrentPatient.Fee = value;
                this.OnPropertyChanged(() => Fee);
            }
        }

        public string FirstName
        {
            get { return this.CurrentPatient.FirstName; }
            set
            {
                this.CurrentPatient.FirstName = value;
                this.OnPropertyChanged(() => FirstName);
            }
        }

        public bool HasPatient
        {
            get { return this.CurrentPatient != null; }
        }

        public DateTime InscriptionDate
        {
            get { return this.CurrentPatient.InscriptionDate; }
            set
            {
                this.CurrentPatient.InscriptionDate = value;
                this.OnPropertyChanged(() => InscriptionDate);
            }
        }

        public ObservableCollection<InsuranceCardDto> Insurances
        {
            get;
            private set;
        }

        public bool IsModified
        {
            get
            {
                return (this.CurrentPatient.FirstName != this.MementoPatient.FirstName
                     || this.CurrentPatient.LastName != this.MementoPatient.LastName
                     || this.CurrentPatient.Fee != this.MementoPatient.Fee
                     || this.CurrentPatient.Birthdate != this.MementoPatient.Birthdate
                     || this.CurrentPatient.PlaceOfBirth != this.MementoPatient.PlaceOfBirth
                     || this.CurrentPatient.Address.Street != this.MementoPatient.Address.Street
                     || this.CurrentPatient.Address.StreetNumber != this.MementoPatient.Address.StreetNumber
                     || this.CurrentPatient.Address.BoxNumber != this.MementoPatient.Address.BoxNumber
                     || this.CurrentPatient.Address.PostalCode != this.MementoPatient.Address.PostalCode
                     || this.CurrentPatient.Address.City != this.MementoPatient.Address.City
                     || this.CurrentPatient.PrivatePhone != this.MementoPatient.PrivatePhone
                     || this.CurrentPatient.PrivateMobile != this.MementoPatient.PrivateMobile
                     || this.CurrentPatient.PrivateMail != this.MementoPatient.PrivateMail
                     || this.CurrentPatient.ProPhone != this.MementoPatient.ProPhone
                     || this.CurrentPatient.ProMobile != this.MementoPatient.ProMobile
                     || this.CurrentPatient.ProMail != this.MementoPatient.ProMail
                     || this.CurrentPatient.InscriptionDate != this.MementoPatient.InscriptionDate
                     || this.CurrentPatient.Reason != this.MementoPatient.Reason
                     || !EqualityHelpers.AreEquals(this.SelectedReputation, this.MementoPatient.Reputation)
                     || !EqualityHelpers.AreEquals(this.SelectedProfession, this.MementoPatient.Profession)
                     || !EqualityHelpers.AreEquals(this.SelectedInsurance, this.MementoPatient.Insurance)
                     || !EqualityHelpers.AreEquals(this.SelectedPractice, this.MementoPatient.Practice)
                     || this.HasChangedDoctors());
            }
        }

        public string LastName
        {
            get { return this.CurrentPatient.LastName; }
            set
            {
                this.CurrentPatient.LastName = value;
                this.OnPropertyChanged(() => LastName);
            }
        }

        public DateTime LastUpdate
        {
            get { return this.CurrentPatient.LastUpdate; }
            set
            {
                this.CurrentPatient.LastUpdate = value;
                this.OnPropertyChanged(() => LastUpdate);
            }
        }

        public string PlaceOfBirth
        {
            get { return this.CurrentPatient.PlaceOfBirth; }
            set
            {
                this.CurrentPatient.PlaceOfBirth = value;
                this.OnPropertyChanged(() => PlaceOfBirth);
            }
        }

        public string PostalCode
        {
            get { return this.CurrentPatient.Address.PostalCode; }
            set
            {
                this.CurrentPatient.Address.PostalCode = value;
                this.OnPropertyChanged(() => PostalCode);
            }
        }

        public ObservableCollection<PracticeCardDto> Practices
        {
            get;
            private set;
        }

        public string PrivateMail
        {
            get { return this.CurrentPatient.PrivateMail; }
            set
            {
                this.CurrentPatient.PrivateMail = value;
                this.OnPropertyChanged(() => PrivateMail);
            }
        }

        public string PrivateMobile
        {
            get { return this.CurrentPatient.PrivateMobile; }
            set
            {
                this.CurrentPatient.PrivateMobile = value;
                this.OnPropertyChanged(() => PrivateMobile);
            }
        }

        public string PrivatePhone
        {
            get { return this.CurrentPatient.PrivatePhone; }
            set
            {
                this.CurrentPatient.PrivatePhone = value;
                this.OnPropertyChanged(() => PrivatePhone);
            }
        }

        public ObservableCollection<ProfessionCardDto> Professions
        {
            get;
            private set;
        }

        public string ProMail
        {
            get { return this.CurrentPatient.ProMail; }
            set
            {
                this.CurrentPatient.ProMail = value;
                this.OnPropertyChanged(() => ProMail);
            }
        }

        public string ProMobile
        {
            get { return this.CurrentPatient.ProMobile; }
            set
            {
                this.CurrentPatient.ProMobile = value;
                this.OnPropertyChanged(() => ProMobile);
            }
        }

        public string ProPhone
        {
            get { return this.CurrentPatient.ProPhone; }
            set
            {
                this.CurrentPatient.ProPhone = value;
                this.OnPropertyChanged(() => ProPhone);
            }
        }

        public string Reason
        {
            get { return this.CurrentPatient.Reason; }
            set
            {
                this.CurrentPatient.Reason = value;
                this.OnPropertyChanged(() => Reason);
            }
        }

        public ObservableCollection<ReputationCardDto> Reputations
        {
            get;
            private set;
        }

        public ObservableCollection<TagViewModel> SearchTags
        {
            get;
            private set;
        }

        public InsuranceCardDto SelectedInsurance
        {
            get { return this.selectedInsurance; }
            set
            {
                this.selectedInsurance = value;
                this.OnPropertyChanged(() => SelectedInsurance);
            }
        }

        public PracticeCardDto SelectedPractice
        {
            get { return this.selectedPractice; }
            set
            {
                this.selectedPractice = value;
                this.OnPropertyChanged(() => SelectedPractice);
            }
        }

        public ProfessionCardDto SelectedProfession
        {
            get { return this.selectedProfession; }
            set
            {
                this.selectedProfession = value;
                this.OnPropertyChanged(() => SelectedProfession);
            }
        }

        public ReputationCardDto SelectedReputation
        {
            get { return this.selectedReputation; }
            set
            {
                this.selectedReputation = value;
                this.OnPropertyChanged(() => SelectedReputation);
            }
        }

        public string Street
        {
            get { return this.CurrentPatient.Address.Street; }
            set
            {
                this.CurrentPatient.Address.Street = value;
                this.OnPropertyChanged(() => Street);
            }
        }

        public string StreetNumber
        {
            get { return this.CurrentPatient.Address.StreetNumber; }
            set
            {
                this.CurrentPatient.Address.StreetNumber = value;
                this.OnPropertyChanged(() => StreetNumber);
            }
        }

        public byte[] Thumbnail
        {
            get { return this.CurrentPatient.Thumbnail; }
            set
            {
                this.CurrentPatient.Thumbnail = value;
                this.OnPropertyChanged(() => Thumbnail);
            }
        }

        public UiMessageDto UiMessage
        {
            get { return this.uiMessage; }
            set
            {
                this.uiMessage = value;
                this.OnPropertyChanged(() => UiMessage);
            }
        }

        private ExpandedPatientDto CurrentPatient
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the memento patient.
        /// On the initialisation, it'll save the state so user will be able to rollback
        /// the patient to this state
        /// </summary>
        /// <value>
        /// The memento patient.
        /// </value>
        private ExpandedPatientDto MementoPatient
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Binds the tag to the patient if it exists. Otherwise, ask to create a tag and bind it to the
        /// patient if the user asked for it.
        /// </summary>
        /// <param name="tagName">The tag.</param>
        public void BindTag(string tagName)
        {
            var tag = new TagDto() { Name = tagName };

            if (!this.Service.HasTag(this.CurrentPatient, tag))
            {
                if (this.Service.Exists(tag))
                {
                    using (WaitingCursor.While) { this.Service.Bind(tag, this.CurrentPatient); }
                }
                else if (ViewService.MessageBox.Question(string.Format(Messages.Msg_AskCreateTag, tagName)))
                {
                    using (WaitingCursor.While) { this.Service.CreateAndBind(tag, this.CurrentPatient); }
                }
                this.RefreshTagsOfPatient();
            }
            else
            {
                //ViewService.MessageBox.Information(Messages.Msg_PatientHasThisTag);
                this.UiMessage = UiMessageDto.Warn(Messages.Msg_PatientHasThisTag);
            }
        }

        public void BindTag(TagDto tag)
        {
            this.Service.Bind(tag, this.CurrentPatient);
            this.RefreshTagsOfPatient();
        }

        public CreateDoctorViewModel BuildCreateDoctorViewModel()
        {
            return new CreateDoctorViewModel(this.ErrorService, this.Service, this.IsAsync);
        }

        public object BuildDoctorPopupViewModel()
        {
            return new DoctorPopupViewModel(this, this.ErrorService, this.Service, this.CurrentPatient, this.CriterionFactory, this.IsAsync);
        }

        public object BuildInsurancePopupViewModel()
        {
            return new InsurancePopupViewModel(this, this.ErrorService, this.Service, this.CurrentPatient, IsAsync);
        }

        public object BuildPracticePopupViewModel()
        {
            return new PracticePopupViewModel(this, this.ErrorService, this.Service, this.CurrentPatient, IsAsync);
        }

        public ITextPopupViewModel BuildProfessionPopupViewModel()
        {
            return new ProfessionPopupViewModel(this, this.ErrorService, this.Service, this.CurrentPatient, IsAsync);
        }

        public ITextPopupViewModel BuildReputationPopupViewModel()
        {
            return new ReputationPopupViewModel(this, this.ErrorService, this.Service, this.CurrentPatient, IsAsync);
        }

        public void Refresh(bool resetChangeTracker = false)
        {
            this.Async(() => new
            {
                MementoPatient = this.Service.Expand(this.___patient),
                CurrentPatient = this.Service.Expand(this.___patient),
                Professions = this.Service.GetAllProfessions(),
                Insurances = this.Service.GetAllInsurances(),
                Practices = this.Service.GetAllPractices(),
                Reputations = this.Service.GetAllReputations(),
            }
            , r =>
            {
                if (!this.isInitialised)
                {
                    this.MementoPatient = r.MementoPatient;
                    this.isInitialised = true;
                }

                this.CurrentPatient = r.CurrentPatient;

                this.SearchTags.Refill(this.ToViewModel(r.CurrentPatient.SearchTags));
                this.Doctors.Refill(ToViewModel(r.CurrentPatient.Doctors));

                this.RefreshProfessions(r.Professions);
                this.RefreshInsurances(r.Insurances);
                this.RefreshPractices(r.Practices);
                this.RefreshReputations(r.Reputations);
                this.OnPropertyChanged(string.Empty); //Notify all the properties

            });
        }

        public void RefreshDoctors()
        {
            this.Async(() => this.Service.Expand(this.CurrentPatient)
                      , r =>
                      {
                          this.CurrentPatient.Doctors = r.Doctors;
                          this.Doctors.Refill(ToViewModel(r.Doctors));
                      });
        }

        public void RefreshInsurance()
        {
            this.Async(() => new
            {
                Insurances = this.Service.GetAllInsurances(),
                RefreshedPatient = this.Service.Expand(this.CurrentPatient)
            }
            , r =>
            {
                // The order is important:
                // first refresh current patient and afterward, refresh professions
                this.CurrentPatient.Insurance = r.RefreshedPatient.Insurance;
                this.RefreshInsurances(r.Insurances);
            });
        }

        public void RefreshProfession()
        {
            this.Async(() => new
            {
                Professions = this.Service.GetAllProfessions(),
                RefreshedPatient = this.Service.Expand(this.CurrentPatient)
            }
            , r =>
            {
                // The order is important:
                // first refresh current patient and afterward, refresh professions
                this.CurrentPatient.Profession = r.RefreshedPatient.Profession;
                this.RefreshProfessions(r.Professions);
            });
        }

        public void RefreshReputation()
        {
            this.Async(() => new
            {
                Reputations = this.Service.GetAllReputations(),
                RefreshedPatient = this.Service.Expand(this.CurrentPatient)
            }
            , r =>
            {
                // The order is important:
                // first refresh current patient and afterward, refresh reptations
                this.CurrentPatient.Reputation = r.RefreshedPatient.Reputation;
                this.RefreshReputations(r.Reputations);
            });
        }

        public void RefreshTags()
        {
            this.Async(() => this.Service.GetAllAssignableTags(this.CurrentPatient),
                        r => this.AllSearchTags.Refill(r));
        }

        public void RefreshTagsOfPatient()
        {
            this.Async(() => this.Service.Expand(this.CurrentPatient).SearchTags
                      , t => this.SearchTags.Refill(this.ToViewModel(t)));
        }

        /// <summary>
        /// Reverts the patient to the state of the last save.
        /// </summary>
        public void RevertPatient()
        {
            this.Async(() => this.Service.Update(this.MementoPatient)
                     , () => this.Refresh(true));
        }

        public void UpdatePatient()
        {
            this.CurrentPatient.Profession = this.SelectedProfession;
            this.CurrentPatient.Insurance = this.SelectedInsurance;
            this.CurrentPatient.Practice = this.SelectedPractice;
            this.CurrentPatient.Reputation = this.SelectedReputation;

            this.Async(() =>
            {
                this.Service.Update(this.CurrentPatient);
                return this.Service.Expand(this.CurrentPatient);
            }
            , r =>
            {
                this.UiMessage = UiMessageDto.Info(Messages.Msg_Saved);
                this.MementoPatient = r;
            });
        }

        internal void RefreshPractice()
        {
            this.Async(() => new
            {
                Practices = this.Service.GetAllPractices(),
                RefreshedPatient = this.Service.Expand(this.CurrentPatient)
            }
            , r =>
            {
                // The order is important:
                // first refresh current patient and afterward, refresh professions
                this.CurrentPatient = r.RefreshedPatient;
                this.RefreshPractices(r.Practices);
            });
        }

        private bool HasChangedDoctors()
        {
            if (this.CurrentPatient.Doctors.Count() != this.MementoPatient.Doctors.Count()) { return true; }
            else
            {
                for (int i = 0; i < this.CurrentPatient.Doctors.Count(); i++)
                {
                    if (this.CurrentPatient.Doctors.ElementAt(i).Id != this.MementoPatient.Doctors.ElementAt(i).Id) { return true; }
                }
                return false;
            }
        }

        private void RefreshInsurances(IEnumerable<InsuranceCardDto> source)
        {
            this.Insurances.Refill(source);
            if (this.CurrentPatient.Insurance != null)
            {
                this.SelectedInsurance = (from i in this.Insurances
                                          where i.Id == this.CurrentPatient.Insurance.Id
                                          select i).SingleOrDefault();
            }
        }

        private void RefreshPractices(IEnumerable<PracticeCardDto> source)
        {
            this.Practices.Refill(source);
            if (this.CurrentPatient.Practice != null)
            {
                this.SelectedPractice = (from i in this.Practices
                                         where i.Id == this.CurrentPatient.Practice.Id
                                         select i).SingleOrDefault();
            }
        }

        private void RefreshProfessions(IEnumerable<ProfessionCardDto> source)
        {
            this.Professions.Refill(source);
            if (this.CurrentPatient.Profession != null)
            {
                this.SelectedProfession = (from p in Professions
                                           where p.Id == this.CurrentPatient.Profession.Id
                                           select p).SingleOrDefault();
            }
        }

        private void RefreshReputations(IEnumerable<ReputationCardDto> source)
        {
            this.Reputations.Refill(source);
            if (this.CurrentPatient.Reputation != null)
            {
                this.SelectedReputation = (from i in this.Reputations
                                           where i.Id == this.CurrentPatient.Reputation.Id
                                           select i).SingleOrDefault();
            }
        }

        private TagViewModel ToViewModel(TagDto tag)
        {
            return new TagViewModel(this, this.ErrorService, this.Service, this.CurrentPatient, tag, this.IsAsync);
        }

        private EditableDoctorCardViewModel ToViewModel(DoctorCardDto doctor)
        {
            return new EditableDoctorCardViewModel(this, this.ErrorService, this.Service, this.CurrentPatient, doctor, this.IsAsync);
        }

        private IEnumerable<TagViewModel> ToViewModel(IEnumerable<TagDto> tags)
        {
            var list = new List<TagViewModel>();
            foreach (var tag in tags)
            {
                list.Add(this.ToViewModel(tag));
            }
            return list;
        }

        private IEnumerable<EditableDoctorCardViewModel> ToViewModel(IEnumerable<DoctorCardDto> doctors)
        {
            var list = new List<EditableDoctorCardViewModel>();
            foreach (var doctor in doctors)
            {
                list.Add(this.ToViewModel(doctor));
            }
            return list;
        }

        #endregion Methods
    }
}